#pragma once

#include "Common.h"

class LinkedList
{
public:
	struct Node
	{
		StudentInfo data;
		Node* next = nullptr;

		friend std::ostream& operator<<(std::ostream& out, const Node& node);

		Node() {}

		explicit Node(const StudentInfo& data)
			: Node(data, nullptr)
		{}

		Node(const StudentInfo& data, Node* next)
			: data(data), next(next)
		{}
	};

public:

	LinkedList()
	{
		dummyHead = CreateNode();
	}

	~LinkedList()
	{
		Node* p = dummyHead->next;
		while (p != nullptr)
		{
			Node* toDelete = p;
			p = p->next;
			delete toDelete;
		}
		delete dummyHead;
	}

	static Node* CreateNode()
	{
		Node* p = new Node;
		if (p == nullptr)
		{
			throw std::exception("failed to create Node");
		}

		return p;
	}

	Node* CreateNode(const StudentInfo& data)
	{
		Node* p = CreateNode();
		p->data = data;
		return p;
	}

	explicit LinkedList(Node* head_in): LinkedList()
	{
		dummyHead->next = head_in;
	}

	Node* GetHead() const
	{
		return dummyHead->next;
	}

	void InsertFront(StudentInfo studentInfo)
	{
		Node* newNode = CreateNode(studentInfo);
		newNode->next = GetHead();
		dummyHead->next = newNode;
		size++;
	}

	void Insert(int index, const StudentInfo& data)
	{
		CheckIndex(index);

		Node* p = FindNodeBeforeIndex(index);

		Node* newNode = CreateNode(data);
		newNode->next = p->next;
		p->next = newNode;
		size++;
	}

	void Delete(int index)
	{
		CheckIndex(index);

		if (size <= 0)
		{
			throw std::exception("Cannot delete element from an empty list.");
		}

		Node* p = FindNodeBeforeIndex(index);
		Node* toDelete = p->next;
		p->next = p->next->next;
		delete toDelete;
		size--;
	}

	StudentInfo& Get(int index)
	{
		CheckIndex(index);

		Node* p = FindNodeBeforeIndex(index);
		return p->next->data;
	}

	Node* FindNodeBeforeIndex(int index) const
	{
		CheckIndex(index);

		int i;
		Node* p;
		for (i = 0, p = dummyHead; i < index - 1; i++, p = p->next)
		{
			// finding the previous node
		}
		return p;
	}

	void Print(int index)
	{
		CheckIndex(index);
		
		std::cout << Get(index) << std::endl;
	}

	void PrintAll()
	{
		std::cout << "LinkedList printing... size: " << size << std::endl;

		Node* p = GetHead();
		while (p != NULL)
		{
			std::cout << *p << std::endl;
			std::cout << "Addr: " << p << ", Next Addr: " << p->next << std::endl;
			p = p->next;
		}
	}

	inline void CheckIndex(int index) const
	{
		if (index < 1 || index > size)
		{
			throw std::exception("Invalid index.");
		}
	}

private:
	Node* dummyHead;
	int size = 0;
};

inline std::ostream& operator<<(std::ostream& out, const LinkedList::Node& node)
{
	return out << node.data;
}